﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Locator.Stores
{
    public static class MapMath
    {
        /// <summary>
        /// Calculates the distance between in km.
        /// </summary>
        /// <param name="latitude1">The latitude1.</param>
        /// <param name="longitude1">The longitude1.</param>
        /// <param name="latitude2">The latitude2.</param>
        /// <param name="longitude2">The longitude2.</param>
        /// <returns></returns>
        public static double CalculateDistanceBetweenInKm(double latitude1, double longitude1, double latitude2, double longitude2)
        {
            double result = 0;
            const int meanRadiusOfEarthInKm = 6371;
            double latitude1InRadians = MapMath.DegreesToRadians(latitude1);
            double longitude1InRadians = MapMath.DegreesToRadians(longitude1);
            double latitude2InRadians = MapMath.DegreesToRadians(latitude2);
            double longitude2InRadians = MapMath.DegreesToRadians(longitude2);
            double latitudeDeltaInRadians = latitude2InRadians - latitude1InRadians;
            double longitudeDeltaInRadians = longitude2InRadians - longitude1InRadians;

            // From http://en.wikipedia.org/wiki/Haversine_formula - distance d between two lat/long coordinates is
            // haversin (d/R) = haversin(lat2 - lat1) + cos(lat1) * cos(lat2) * haversin(long2 - long1)
            // where R is the radius of the sphere

            // Calculate left side of above identity
            double haversineOfDistanceDividedByEarthRadius = MapMath.Haversin(latitudeDeltaInRadians) + Math.Cos(latitude1InRadians) * Math.Cos(latitude2InRadians) * MapMath.Haversin(longitudeDeltaInRadians);
            // Calculate inverse of the result
            double distanceDividedByEarthRadius = MapMath.InverseHaversin(haversineOfDistanceDividedByEarthRadius);
            // Multiply by mean radius of Earth to get distance.  For this program this is accurate enough even though Earth is not a perfect sphere.
            result = distanceDividedByEarthRadius * meanRadiusOfEarthInKm;
            return result;
        }

        /// <summary>
        /// Haversins the specified d.
        /// </summary>
        /// <param name="d">The d.</param>
        /// <returns></returns>
        public static double Haversin(double d)
        {
            // From http://en.wikipedia.org/wiki/Haversine_formula
            // haversin (x) = (sin(x/2))^2
            // where R is the radius of the sphere
            double result = Math.Pow(Math.Sin(d / 2), 2);
            return result;
        }

        /// <summary>
        /// Inverses the haversin.
        /// </summary>
        /// <param name="d">The number in radians.</param>
        /// <returns></returns>
        private static double InverseHaversin(double d)
        {
            // From http://en.wikipedia.org/wiki/Haversine_formula
            // inverse haversin(x) = 2 * arcsin(x^1/2)
            double result = 2 * Math.Asin(Math.Sqrt(d));
            return result;
        }

        /// <summary>
        /// Degreeses to radians.
        /// </summary>
        /// <param name="d">The number in degrees.</param>
        /// <returns></returns>
        private static double DegreesToRadians(double d)
        {
            return d * Math.PI / 180;
        }
    }
}
